/*
 * Copyright (C) 2011, EADS France
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.interpreter.backends.rete;

import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Laurent WOUTERS
 */
public class BetaNegativeJoinNode implements TokenHolder, TokenActivable, FactActivable {
    private FactHolder alphaMem;
    private TokenHolder betaMem;
    private List<BetaJoinNodeTest> tests;
    private TokenActivable child;
    private Map<Token, List<TripleFact>> matches;
    
    @Override public Collection<Token> getTokens() { return betaMem.getTokens(); }
    @Override public Collection<TokenActivable> getChildren() {
        Collection<TokenActivable> list = new ArrayList<TokenActivable>();
        if (child != null)
            list.add(child);
        return list;
    }
    @Override public void addChild(TokenActivable activable) { this.child = activable; }
    @Override public void removeChild(TokenActivable activable) { this.child = null; }
    @Override public void removeAllChildren() { this.child = null; }
    
    public BetaNegativeJoinNode(FactHolder alpha, TokenHolder beta, Collection<BetaJoinNodeTest> tests) {
        this.alphaMem = alpha;
        this.betaMem = beta;
        this.tests = new ArrayList<BetaJoinNodeTest>(tests);
        this.alphaMem.addChild(this);
        this.betaMem.addChild(this);
        this.matches = new IdentityHashMap<Token, List<TripleFact>>();
    }
    
    @Override public void activateToken(Token token) {
        for (TripleFact fact : alphaMem.getFacts())
            applyPositive(token, fact);
        if (!matches.containsKey(token))
            child.activateToken(token);
    }
    @Override public void deactivateToken(Token token) {
        matches.remove(token);
        child.deactivateToken(token);
    }
    @Override public void activateTokens(Collection<Token> tokens) {
        Iterator<Token> iter = tokens.iterator();
        while (iter.hasNext()) {
            Token token = iter.next();
            for (TripleFact fact : alphaMem.getFacts())
                applyPositive(token, fact);
            if (matches.containsKey(token))
                iter.remove();
        }
        int size = tokens.size();
        if (size != 0) {
            if (size == 1)
                child.activateToken(tokens.iterator().next());
            else
                child.activateTokens(tokens);
        }
    }
    @Override public void deactivateTokens(Collection<Token> tokens) {
        for (Token token : tokens)
            matches.remove(token);
        child.deactivateTokens(tokens);
    }
    
    @Override public void activateFact(TripleFact fact) {
        for (Token token : betaMem.getTokens()) {
            applyPositive(token, fact);
            if (matches.containsKey(token))
                child.deactivateToken(token);
        }
    }
    @Override public void deactivateFact(TripleFact fact) {
        for (Token token : betaMem.getTokens()) {
            if (applyNegative(token, fact)) {
                child.activateToken(token);
            }
        }
    }
    @Override public void activateFacts(Collection<TripleFact> facts) {
        List<Token> deactivated = new ArrayList<Token>();
        for (TripleFact fact : facts) {
            for (Token token : betaMem.getTokens()) {
                applyPositive(token, fact);
                if (matches.containsKey(token))
                    deactivated.add(token);
            }
        }
        int size = deactivated.size();
        if (size != 0) {
            if (size == 1)
                child.deactivateToken(deactivated.get(0));
            else
                child.deactivateTokens(new FastBuffer<Token>(deactivated));
        }
    }
    @Override public void deactivateFacts(Collection<TripleFact> facts) {
        List<Token> reactivated = new ArrayList<Token>();
        for (TripleFact fact : facts) {
            for (Token token : betaMem.getTokens()) {
                if (applyNegative(token, fact))
                    reactivated.add(token);
            }
        }
        int size = reactivated.size();
        if (size != 0) {
            if (size == 1)
                child.activateToken(reactivated.get(0));
            else
                child.activateTokens(new FastBuffer<Token>(reactivated));
        }
    }
    
    private void applyPositive(Token token, TripleFact fact) {
        for (BetaJoinNodeTest test : tests) {
            if (!test.check(token, fact)) {
                return;
            }
        }
        List<TripleFact> tsm = matches.get(token);
        if (tsm == null) {
            tsm = new ArrayList<TripleFact>();
            matches.put(token, tsm);
        }
        tsm.add(fact);
    }
    
    private boolean applyNegative(Token token, TripleFact fact) {
        List<TripleFact> tsm = matches.get(token);
        if (tsm == null)
            return false;
        if (!tsm.remove(fact))
            return false;
        if (!tsm.isEmpty())
            return false;
        matches.remove(token);
        return true;
    }
}
